/*
 * Copyright 2012 Finbudget.com
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package finbudgettable.client.ui.grid.cell.suggest;

import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Document;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.i18n.client.Constants;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.web.bindery.event.shared.EventBus;

import finbudget.client.event.ShowInfoMessageEvent;
import finbudget.client.general.NodeSuggestionOracle;
import finbudgettable.client.ui.grid.cell.AbstractPopupCell;

public class SuggestCell extends AbstractPopupCell<String, SuggestCellEditor> {

    public static final int MIN_EDITOR_WIDTH = 200;

    interface Resources extends ClientBundle {

        interface Style extends CssResource {
            @ClassName("b-suggest-cell__text")
            String bSuggestCell__text();

            @ClassName("b-suggest-cell__text_loading")
            String bSuggestCell__text_loading();

            @ClassName("b-suggest-cell-editor__input")
            String bSuggestCellEditor__input();

            @ClassName("b-suggest-cell-editor__suggest")
            String bSuggestCellEditor__suggest();

            @ClassName("b-suggest-cell__multiline_text_loading")
            String bSuggestCell__multiline_text_loading();

            @ClassName("b-suggest-cell__multiline_text")
            String bSuggestCell__multiline_text();

            @ClassName("b-suggest-cell")
            String bSuggestCell();

            @ClassName("b-suggest-filter-panel")
            String bSuggestFilterPanel();
        }

        @Source("SuggestCell.css")
        Style style();
    }

    interface Template extends SafeHtmlTemplates {
        @Template("<div class=\"{0}\">{1}</div>")
        SafeHtml text(String className, String value);

        @Template("<div id=\"{0}\" class=\"{1}\">{2}</div>")
        SafeHtml textWithId(String id, String className, String value);

        @Template("<a tabindex=\"-1\"><div class=\"{0}\">{1}</div></a>")
        SafeHtml loading(String className, String value);
    }

    interface Strings extends Constants {
        @DefaultStringValue("загрузка данных...")
        String loading();

        @DefaultStringValue("Выбранное значение скопировано в буфер приложения. Для вставки используйте комбинацию Shift+Click")
		String valueCopied();
    }

    private static final Template TEMPLATE = GWT.create(Template.class);
    private static final Strings STRINGS = GWT.create(Strings.class);
    static final Resources RESOURCES = GWT.create(Resources.class);

    static {
        RESOURCES.style().ensureInjected();
    }

    private Object lastLoadingKey;
    private final NodeSuggestionOracle oracle;
    private final boolean nullable;
    private final EventBus eventBus;

    public SuggestCell(boolean nullable, NodeSuggestionOracle oracle, EventBus eventBus) {
        super(new SuggestCellEditor(nullable, oracle));
        this.oracle = oracle;
        this.nullable = nullable;
        this.eventBus = eventBus;
    }

    private String getLoadingTextClassName() {
        if (isMultilineStyle()) {
            return RESOURCES.style().bSuggestCell__multiline_text_loading();
        } else {
            return RESOURCES.style().bSuggestCell__text_loading();
        }
    }

    private String getTextClassName() {
        if (isMultilineStyle()) {
            return RESOURCES.style().bSuggestCell__multiline_text();
        } else {
            return RESOURCES.style().bSuggestCell__text();
        }
    }

    @Override
    protected void render(Context context, String value, String viewData, SafeHtmlBuilder sb) {
        boolean waitForList = !isListLoaded() && isWaitingForList(context);
        if (waitForList) {
            sb.append(TEMPLATE.loading(getLoadingTextClassName(), STRINGS.loading()));
        } else {
            String key = viewData != null ? viewData : value;
            boolean wasWaitingForList = isWaitingForList(context);
            lastLoadingKey = null;
            String text = key != null ? oracle.getItemNameByKey(key) : "";
            if (wasWaitingForList) {
                final String id = Document.get().createUniqueId();
                sb.append(TEMPLATE.textWithId(id, getTextClassName(), text));
                Scheduler.get().scheduleDeferred(new Scheduler.ScheduledCommand() {
                    @Override
                    public void execute() {
                        Element el = Document.get().getElementById(id);
                        if (el != null) {
                            NativeEvent event = Document.get().createClickEvent(0, 0, 0, 0, 0, true, true, true, true);
                            el.dispatchEvent(event);
                        }
                    }
                });
            } else {
                sb.append(TEMPLATE.text(getTextClassName(), text));
            }
        }
    }

    private boolean isWaitingForList(Context context) {
        return lastLoadingKey != null && lastLoadingKey.equals(context.getKey());
    }

    @Override
    protected void popup(final SuggestCellEditor popupEditor) {
        popupEditor.setPopupPositionAndShow(new PopupPanel.PositionCallback() {
            public void setPosition(int offsetWidth, int offsetHeight) {
                Element td = getLastParent().getParentElement();

                int height = td.getAbsoluteBottom() - td.getAbsoluteTop();
                popupEditor.setSuggestBoxHeight((height - 1) + "px");

                int left = td.getAbsoluteLeft() - popupEditor.getSuggestBoxLeftOffset();
                int top = td.getAbsoluteTop() - popupEditor.getSuggestBoxTopOffset();
                popupEditor.setPopupPosition(left, top);

                int width = td.getAbsoluteRight() - td.getAbsoluteLeft();
                if (width < MIN_EDITOR_WIDTH) {
                    width = MIN_EDITOR_WIDTH;
                }
                popupEditor.setSuggestBoxWidth(width + "px");
            }
        });
    }

    private boolean isListLoaded() {
        return oracle.getListItemsQty() > 0;
    }

    @Override
    public void startEdit(Context context, Element parent, String value, NativeEvent event,
                          ValueUpdater<String> valueUpdater) {
        if (isListLoaded()) {
            super.startEdit(context, parent, value, event, valueUpdater);
        } else {
            parent.setInnerHTML(TEMPLATE.loading(getLoadingTextClassName(), STRINGS.loading()).asString());
            lastLoadingKey = context.getKey();
            parent.getFirstChildElement().focus();
        }
    }

    private static String clipboardValue;

    @Override
    public void onBrowserEvent(Context context, Element parent, String value, NativeEvent event, ValueUpdater<String> valueUpdater) {
        String type = event.getType();
        if (BrowserEvents.CLICK.equals(type)) {
        	if (event.getCtrlKey()) {
        		// Copy current value
        		clipboardValue = value;
        		eventBus.fireEvent(new ShowInfoMessageEvent(STRINGS.valueCopied()));
        		return;
        	} else if (event.getShiftKey()) {
        		valueUpdater.update(clipboardValue);
                SafeHtmlBuilder sb = new SafeHtmlBuilder();
                render(context, clipboardValue, sb);
                parent.setInnerHTML(sb.toSafeHtml().asString());
        		return;
        	}
        }
        if (isWaitingForList(context) && !isListLoaded() && BrowserEvents.BLUR.equals(type)) {
            lastLoadingKey = null;
            SafeHtmlBuilder sb = new SafeHtmlBuilder();
            render(context, value, sb);
            parent.setInnerHTML(sb.toSafeHtml().asString());
        } else if (BrowserEvents.KEYDOWN.equals(type) && event.getKeyCode() == KeyCodes.KEY_DELETE) {
        	if (nullable) {
        		valueUpdater.update(null);
                SafeHtmlBuilder sb = new SafeHtmlBuilder();
                render(context, null, sb);
                parent.setInnerHTML(sb.toSafeHtml().asString());
        	}
        } else {
            super.onBrowserEvent(context, parent, value, event, valueUpdater);
        }
    }

}
